<?php

/**
  +------------------------------------------------------------------------------
 * Redis操作类，支持 Master/Slave 的负载集群
  +------------------------------------------------------------------------------
 * @author Cary Cui
 * @date 2014-11-13
  +------------------------------------------------------------------------------
 */
class DbYspRedis {

    //是否使用 M/S 的读写集群方案
    private $is_use_cluster = false;
    //Slave句柄标记
    private $sconn = 0;
    //服务器连接句柄
    private $mconn = array(
        //只支持一台 Master
        'master' => null,
        //可以有多台 Slave
        'slave' => array()
    );
    //最终的操作句柄
    private $redis_conn = null;

    /**
      +--------------------------------------------------------------------------
     * 连接服务器,注意：这里使用长连接，提高效率，但不会自动关闭
      +--------------------------------------------------------------------------
     * @param array $config Redis服务器配置
     * @param boolean $is_master 当前添加的服务器是否为 Master 服务器
     * @return boolean
      +--------------------------------------------------------------------------
     */
    function __construct($config, $is_master = true, $slave_one = true) {
        //设置 Master 连接
        if ($is_master) {
            $this->mconn['master'] = new Redis();
            $this->mconn['master']->pconnect($config['host'], $config['port']);
            $this->mconn['master']->auth($config['password']);
        } else {
            //多个 Slave 连接
            $this->mconn['slave'][$this->sconn] = new Redis();
            $this->mconn['slave'][$this->sconn]->pconnect($config['host'], $config['port']);
            $this->mconn['slave'][$this->sconn]->auth($config['password']);
            ++$this->sconn;
        }
        $this->getRedis($is_master, $slave_one);
    }

    /**
      +--------------------------------------------------------------------------
     * 得到 Redis 原始对象可以有更多的操作
      +--------------------------------------------------------------------------
     * @param boolean $is_master 返回服务器的类型 true:返回Master false:返回Slave
     * @param boolean $slave_one 返回的Slave选择 true:负载均衡随机返回一个Slave选择 false:返回所有的Slave选择
     * @return redis object
      +--------------------------------------------------------------------------
     */
    function getRedis($is_master, $slave_one) {
        if ($is_master) {
            $this->redis_conn = $this->mconn['master'];
        } else {
            $this->redis_conn = ($slave_one ? $this->_getSlaveRedis() : $this->mconn['slave']);
        }
    }

    /**
      +--------------------------------------------------------------------------
     * 关闭连接
      +--------------------------------------------------------------------------
     * @param int $flag 关闭选择 0:关闭 Master 1:关闭 Slave 2:关闭所有
     * @return boolean
      +--------------------------------------------------------------------------
     */
    function close($flag = 0) {
        switch ($flag) {
            //关闭 Master
            case 0:
                $this->redis_conn->close();
                break;
            //关闭 Slave
            case 1:
                for ($i = 0; $i < $this->sconn; ++$i) {
                    $this->mconn['slave'][$i]->close();
                }
                break;
            //关闭所有
            case 2:
                $this->redis_conn->close();
                for ($i = 0; $i < $this->sconn; ++$i) {
                    $this->mconn['slave'][$i]->close();
                }
                break;
        }
    }

    /**
      +--------------------------------------------------------------------------
     * 随机 HASH 得到 Redis Slave 服务器句柄
      +--------------------------------------------------------------------------
     * @return redis object
      +--------------------------------------------------------------------------
     */
    private function _getSlaveRedis() {
        //就一台 Slave 机直接返回
        if ($this->sconn <= 1) {
            return $this->mconn['slave'][0];
        }
        //随机 Hash 得到 Slave 的句柄
        $hash = $this->_hashId(mt_rand(), $this->sconn);
        return $this->mconn['slave'][$hash];
    }

    /**
      +--------------------------------------------------------------------------
     * 根据ID得到 hash 后 0～m-1 之间的值
      +--------------------------------------------------------------------------
     * @param string $id
     * @param int $m
     * @return int
      +--------------------------------------------------------------------------
     */
    private function _hashId($id, $m = 10) {
        //把字符串K转换为 0～m-1 之间的一个值作为对应记录的散列地址
        $k = md5($id);
        $l = strlen($k);
        $b = bin2hex($k);
        $h = 0;
        for ($i = 0; $i < $l; $i++) {
            //相加模式HASH
            $h += substr($b, $i * 2, 2);
        }
        $hash = ($h * 1) % $m;
        return $hash;
    }

    /**
      +--------------------------------------------------------------------------
     * 写内存
      +--------------------------------------------------------------------------
     * @param string $key 缓存KEY
     * @param string $value 缓存值
     * @param int $expire 过期时间， 0:表示无过期时间
      +--------------------------------------------------------------------------
     */
    function set($key, $value, $expire = 0) {
        //永不超时
        if ($expire == 0) {
            $ret = $this->redis_conn->set($key, $value);
        } else {
            $ret = $this->redis_conn->setex($key, $expire, $value);
        }
        return $ret;
    }

    /**
      +--------------------------------------------------------------------------
     * 读内存
      +--------------------------------------------------------------------------
     * @param string $key 缓存KEY,支持一次取多个 $key = array('key1','key2')
     * @return string || boolean  失败返回 false, 成功返回字符串
      +--------------------------------------------------------------------------
     */
    function get($key) {
        //是否一次取多个值
        $func = is_array($key) ? 'mGet' : 'get';
        //没有使用M/S
        if (!$this->is_use_cluster) {
            return $this->redis_conn->{$func}($key);
        }
        //使用了 M/S
        return $this->_getSlaveRedis()->{$func}($key);
    }

    /**
      +--------------------------------------------------------------------------
     * 条件形式设置缓存，如果 key 不存时就设置，存在时设置失败
      +--------------------------------------------------------------------------
     * @param string $key 缓存KEY
     * @param string $value 缓存值
     * @return boolean
      +--------------------------------------------------------------------------
     */
    function setnx($key, $value) {
        return $this->redis_conn->setnx($key, $value);
    }

    /**
      +--------------------------------------------------------------------------
     * 删除缓存
      +--------------------------------------------------------------------------
     * @param string || array $key 缓存KEY，支持单个健:"key1" 或多个健:array('key1','key2')
     * @return int 删除的健的数量
      +--------------------------------------------------------------------------
     */
    function remove($key) {
        //$key => "key1" || array('key1','key2')
        return $this->redis_conn->delete($key);
    }

    /**
      +--------------------------------------------------------------------------
     * 值加加操作,类似 ++$i ,如果 key 不存在时自动设置为 0 后进行加加操作
      +--------------------------------------------------------------------------
     * @param string $key 缓存KEY
     * @param int $default 操作时的默认值
     * @return int　返回当前累加后的值
      +--------------------------------------------------------------------------
     */
    function incr($key, $default = 1) {
        if ($default == 1) {
            return $this->redis_conn->incr($key);
        } else {
            return $this->redis_conn->incrBy($key, $default);
        }
    }

    /**
      +--------------------------------------------------------------------------
     * 值减减操作,类似 --$i ,如果 key 不存在时自动设置为 0 后进行减减操作
      +--------------------------------------------------------------------------
     * @param string $key 缓存KEY
     * @param int $default 操作时的默认值
     * @return int　返回当前累减后的值
      +--------------------------------------------------------------------------
     */
    function decr($key, $default = 1) {
        if ($default == 1) {
            return $this->redis_conn->decr($key);
        } else {
            return $this->redis_conn->decrBy($key, $default);
        }
    }

    /**
      +--------------------------------------------------------------------------
     * 添空当前数据库
      +--------------------------------------------------------------------------
     * @return boolean
      +--------------------------------------------------------------------------
     */
    function clear() {
        return $this->redis_conn->flushDB();
    }

    /**
      +--------------------------------------------------------------------------
     * 创建一个哈希值
      +--------------------------------------------------------------------------
     */
    function hset($name, $key, $value) {
        if (is_array($value)) {
            return $this->redis_conn->hset($name, $key, serialize($value));
        }
        return $this->redis_conn->hset($name, $key, $value);
    }

    /**
      +--------------------------------------------------------------------------
     * 获取一个哈希值
      +--------------------------------------------------------------------------
     */
    function hget($name, $key = null, $serialize = true) {
        if ($key) {
            $row = $this->redis_conn->hget($name, $key);
            if ($row && $serialize) {
                unserialize($row);
            }
            return $row;
        }
        return $this->redis_conn->hgetAll($name);
    }

    /**
      +--------------------------------------------------------------------------
     * 设置并限定一个LIST的内容
      +--------------------------------------------------------------------------
     */
    function lsSetLimit($key, $value, $start, $end) {
        //从左侧压入一个新值
        $this->redis_conn->lpush($key, $value);
        //限制最终的条数
        return $this->redis_conn->ltrim($key, $start, $end);
    }
    
    /**
      +--------------------------------------------------------------------------
     * 返回存储在 key 的列表里指定范围内的元素。list的第一个元素下标是0
     * @param int start 偏移量基于0的起始下标
     * @param int end 偏移量基于0的终止下标
      +--------------------------------------------------------------------------
     */
    function lsLrange($key,$start,$end) {
        return $this->redis_conn->LRANGE($key, $start, $end);
    }

}
